package jsr292.java.lang.invoke;

import static jsr292.java.lang.invoke.Conversions.convert;
import jsr292.weaver.Debug;
import jsr292.weaver.opt.OptimizerContext;
import jsr292.weaver.opt.OptimizerSnippets;
import jsr292.weaver.opt.OptimizerVarAllocator;

class MHConverter extends MethodHandle {
    private final MethodHandle mh;
    private final MethodType rawMHType;
    private final MethodType rawNewType;

    public MHConverter(MethodHandle mh, MethodType newType, MethodType rawMHType, MethodType rawNewType) {
        super(newType);
        this.mh = mh;
        this.rawMHType=rawMHType;
        this.rawNewType=rawNewType;
    }

    @Override
    public Object $invoke$() throws Throwable {
        return convert(rawMHType.returnType(), rawNewType.returnType(), mh.$invoke$());
    }
    
    @Override
    public Object $invoke$(Object o1) throws Throwable {
        MethodType rawMHType=this.rawMHType;
        MethodType rawNewType=this.rawNewType;
        return convert(rawMHType.returnType(), rawNewType.returnType(),
            mh.$invoke$(convert(rawNewType.parameterType(0), rawMHType.parameterType(0), o1)));
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2) throws Throwable {
        MethodType rawMHType=this.rawMHType;
        MethodType rawNewType=this.rawNewType;
        return convert(rawMHType.returnType(), rawNewType.returnType(),
            mh.$invoke$(convert(rawNewType.parameterType(0), rawMHType.parameterType(0), o1),
                        convert(rawNewType.parameterType(1), rawMHType.parameterType(1), o2)));
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3) throws Throwable {
        MethodType rawMHType=this.rawMHType;
        MethodType rawNewType=this.rawNewType;
        return convert(rawMHType.returnType(), rawNewType.returnType(),
            mh.$invoke$(convert(rawNewType.parameterType(0), rawMHType.parameterType(0), o1),
                        convert(rawNewType.parameterType(1), rawMHType.parameterType(1), o2),
                        convert(rawNewType.parameterType(2), rawMHType.parameterType(2), o3)));
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4) throws Throwable {
        MethodType rawMHType=this.rawMHType;
        MethodType rawNewType=this.rawNewType;
        return convert(rawMHType.returnType(), rawNewType.returnType(),
            mh.$invoke$(convert(rawNewType.parameterType(0), rawMHType.parameterType(0), o1),
                        convert(rawNewType.parameterType(1), rawMHType.parameterType(1), o2),
                        convert(rawNewType.parameterType(2), rawMHType.parameterType(2), o3),
                        convert(rawNewType.parameterType(3), rawMHType.parameterType(3), o4)));
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5) throws Throwable {
        MethodType rawMHType=this.rawMHType;
        MethodType rawNewType=this.rawNewType;
        return convert(rawMHType.returnType(), rawNewType.returnType(),
            mh.$invoke$(convert(rawNewType.parameterType(0), rawMHType.parameterType(0), o1),
                        convert(rawNewType.parameterType(1), rawMHType.parameterType(1), o2),
                        convert(rawNewType.parameterType(2), rawMHType.parameterType(2), o3),
                        convert(rawNewType.parameterType(3), rawMHType.parameterType(3), o4),
                        convert(rawNewType.parameterType(4), rawMHType.parameterType(4), o5)));
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5, Object o6) throws Throwable {
        MethodType rawMHType=this.rawMHType;
        MethodType rawNewType=this.rawNewType;
        return convert(rawMHType.returnType(), rawNewType.returnType(),
            mh.$invoke$(convert(rawNewType.parameterType(0), rawMHType.parameterType(0), o1),
                        convert(rawNewType.parameterType(1), rawMHType.parameterType(1), o2),
                        convert(rawNewType.parameterType(2), rawMHType.parameterType(2), o3),
                        convert(rawNewType.parameterType(3), rawMHType.parameterType(3), o4),
                        convert(rawNewType.parameterType(4), rawMHType.parameterType(4), o5),
                        convert(rawNewType.parameterType(5), rawMHType.parameterType(5), o6)));
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7) throws Throwable {
        MethodType rawMHType=this.rawMHType;
        MethodType rawNewType=this.rawNewType;
        return convert(rawMHType.returnType(), rawNewType.returnType(),
            mh.$invoke$(convert(rawNewType.parameterType(0), rawMHType.parameterType(0), o1),
                        convert(rawNewType.parameterType(1), rawMHType.parameterType(1), o2),
                        convert(rawNewType.parameterType(2), rawMHType.parameterType(2), o3),
                        convert(rawNewType.parameterType(3), rawMHType.parameterType(3), o4),
                        convert(rawNewType.parameterType(4), rawMHType.parameterType(4), o5),
                        convert(rawNewType.parameterType(5), rawMHType.parameterType(5), o6),
                        convert(rawNewType.parameterType(5), rawMHType.parameterType(5), o7)));
    }
    
    @Override
    public Object $invoke$(Object... rest) throws Throwable {
        MethodType rawMHType=this.rawMHType;
        MethodType rawNewType=this.rawNewType;
        int length = rest.length;
        for(int i=0; i<length; i++) {
            rest[i] = convert(rawNewType.parameterType(i), rawMHType.parameterType(i), rest[i]);
        }
        return convert(rawMHType.returnType(), rawNewType.returnType(),
            mh.$invoke$(rest));
    }

    @Override
    public boolean isWeavable(Class<?> classBeingRedefined) {
        return mh.isWeavable(classBeingRedefined);
    }
    @Override
    public void weave(OptimizerContext context, OptimizerVarAllocator allocator) {
        if (Debug.DEBUG) {
            Debug.checkAllocator(allocator, this);
        }
        
        OptimizerSnippets.convert(context, allocator, mh, type());
    }
}
